﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using SuperSerialization.Tree;

namespace SuperSerialization.TreeVisitor
{
    /// <summary>
    /// Visits a document tree constructed from an in-memory object and builds a string representation.
    /// </summary>
    class SerializationVisitor : AbstractVisitor
    {
        /// <summary>
        /// Whether or not semicolons should be used in the output.
        /// </summary>
        static bool semicolons = true;

        /// <summary>
        /// The text writer for output.
        /// </summary>
        TextWriter writer;

        /// <summary>
        /// A setting which determines spacing and miscellaneous readability rules.
        /// </summary>
        bool humanReadable;

        /// <summary>
        /// The current indentation level.
        /// </summary>
        int indentLevel = 0;

        /// <summary>
        /// Constructs a serializing visitor.
        /// </summary>
        /// <param name="writer">A writer for output.</param>
        /// <param name="humanReadable">A setting which determines miscellaneous readability rules, such as whether to use line breaks.</param>
        public SerializationVisitor(TextWriter writer, bool humanReadable)
        {
            this.humanReadable = humanReadable;
            this.writer = writer;
            if (writer == null)
                this.writer = Console.Out;
        }

        public override void VisitLiteral(Tree.LiteralNode literal)
        {
            if (literal.Value is String) Write("\"");
            if (literal.Value != null)
                Write(literal.Value.ToString());
            else
                Write("null");
            if (literal.Value is String) Write("\"");
            if (literal.Value is float) Write("f");
        }

        public override void VisitObject(Tree.ObjectNode obj)
        {
            Write(obj.Type);
            // TODO: Only write the braces if there are fields inside.
            Write(" {");
            indentLevel++;
            if (obj.Members.Count > 0)
            {
                WriteLine();
                foreach (FieldNode value in obj.Members)
                {
                    Indent();
                    value.Accept(this);
                }
            }
            indentLevel--;
            Indent();
            Write("}");
        }

        public override void VisitField(Tree.FieldNode value)
        {
            Write(value.Name + " : ");
            bool isObject = value.Value is ObjectNode;
            if (isObject) { WriteLine(); indentLevel++; Indent(); }
            value.Value.Accept(this);
            if (isObject) { indentLevel--; }
            WriteSemicolon();
            WriteLine();
        }

        public override void VisitArray(ArrayNode array)
        {
            bool isObjectArray = array.Values.Length > 0 && array.Values.GetValue(0) is ObjectNode;
            Write("[");
            if (isObjectArray) indentLevel++;
            for (int i = 0; i < array.Values.Length; i++)
            {
                if (isObjectArray) { WriteLine(); Indent(); }
                array.Values[i].Accept(this);
                if (i < array.Values.Length - 1)
                    Write(", ");
            }
            if (isObjectArray) { indentLevel--; WriteLine(); Indent(); }
            Write("]");
        }

        public override void VisitBase(BaseNode node)
        {
            foreach(Node child in node.Values)
            {
                child.Accept(this);
            }
        }

        public override void VisitNamespace(NamespaceNode node)
        {
            Write("namespace " + node.Namespace);
            WriteSemicolon();
        }

        protected void WriteLine()
        {
            WriteLine("");
        }

        protected void WriteLine(String value)
        {
            if (humanReadable)
            {
                writer.WriteLine(value);
            }
            else
            {
                writer.Write(value);
                writer.Write(" ");
            }
        }

        protected void Write(String value)
        {
            writer.Write(value);
        }

        protected void WriteSemicolon()
        {
            if (semicolons && !humanReadable) Write(";");
        }

        protected void Indent()
        {
            if (!humanReadable) return;
            for (int i = 0; i < indentLevel; i++)
                writer.Write("\t");
        }
    }
}
